export function patch(oldVnode, vnode) {
  /*
   * 如果节点是元素节点，则 nodeType 属性将返回 1
   * 虚拟dom没有nodeType属性
   */
  if (oldVnode.nodeType === 1) {
    let el = createEl(vnode)
    // 获取父节点、插入、删除
    let parentEl = oldVnode.parentNode;
    parentEl.insertBefore(el, oldVnode.nextsibling);
    parentEl.removeChild(oldVnode);
    return el;
  } else { // 使用diff算法进行比对，主要目的是为了复用，属性(修改、删除、添加)、el替换，递归，其它全走旧删除，新添加，减少真实dom操作
    // console.log(oldVnode, vnode);
    // 1、元素不一样
    if (oldVnode.tag !== vnode.tag) {
      oldVnode.el.parentNode.replaceChild(createEl(vnode), oldVnode.el);
    } else {
      // 2、元素一样
      // 2.1、文本不一样
      if (!oldVnode.tag) { // 文本节点
        if (oldVnode.text !== vnode.text) {
          return oldVnode.el.textContent = vnode.text;
        }
      }
      // 2.2、属性不一样
      let el = vnode.el = oldVnode.el;
      updateProperty(vnode, oldVnode.data);

      let oldChildren = oldVnode.children || [];
      let newChildren = vnode.children || [];
      let oldLen = oldChildren.length;
      let newLen = newChildren.length;
      if (oldLen && newLen) { // old有children，new有children
        updateChild(oldChildren, newChildren, el);
      } else if (!oldLen && newLen) { // old没有children，new有children
        for (let i = 0; i < newLen; i++) {
          let child = newChildren[i];
          el.appendChild(createEl(child));
        }
      } else if (oldLen && !newLen) { // old有children，new没有children
        el.innerHTML = "";
      }
    }
  }
}

function updateChild(oldChildren, newChildren, el) {
  let oldStartIndex = 0; // 开始指针索引
  let oldStartVnode = oldChildren[oldStartIndex]; // 开始指针元素
  let oldEndIndex = oldChildren.length - 1; // 结尾指针索引
  let oldEndVnode = oldChildren[oldEndIndex]; // 结尾指针元素

  let newStartIndex = 0; // 开始指针索引
  let newStartVnode = newChildren[newStartIndex]; // 开始指针元素
  let newEndIndex = newChildren.length - 1; // 结尾指针索引
  let newEndVnode = newChildren[newEndIndex]; // 结尾指针元素

  // 创建oldChildren的映射表
  function makeIndexByKey(child) {
    let map = {};
    child.forEach((item, index) => {
      if (item.key) {
        map[item.key] = index;
      }
    })
    return map;
  }
  let map = makeIndexByKey(oldChildren);

  function isSameVnode(a, b) {
    // 这里就用到了v-for循环时所设置的"key"
    return a.tag === b.tag && a.key === b.key;
  }

  // 实际作用：找到对应的 newStartIndex 和 newEndIndex
  // 在这里进行diff比对，实际就是为了进行最大程度的复用，最小程度的创建，唯一的key有助于此目的
  /**
   * 比如：用数组索引，满足isSameVnode，它会走"头头"比对，里面的文本不一样，会进行patch的文本替换
   *  旧：
   *  <li key="0">苹果</li>
   *  <li key="1">桃子</li>
   *  <li key="2">西瓜</li>
   *  新：
   *  <li key="0">西瓜</li>
   *  <li key="1">桃子</li>
   *  <li key="2">苹果</li>
   * 
   * 比如：用唯一值做key，它会走"交叉头尾"比对，里面的文本一样，会复用，开销较小
   * 也就是，一般来说，用唯一key，分配到一样的vnode进行patch的可能性更大
   *  旧：
   *  <li key="苹果">苹果</li>
   *  <li key="桃子">桃子</li>
   *  <li key="西瓜">西瓜</li>
   *  新：
   *  <li key="西瓜">西瓜</li>
   *  <li key="桃子">桃子</li>
   *  <li key="苹果">苹果</li>
   */

  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    // 同样的tag且key相同时
    if (isSameVnode(oldStartVnode, newStartVnode)) { // 头头比
      patch(oldStartVnode, newStartVnode); // 递归
      // 移动指针元素、开始指针索引+1
      oldStartVnode = oldChildren[++oldStartIndex];
      newStartVnode = newChildren[++newStartIndex];
    } else if (isSameVnode(oldEndVnode, newEndVnode)) { // 尾尾比
      patch(oldEndVnode, newEndVnode); // 递归
      // 移动指针元素、结束指针索引-1
      oldEndVnode = oldChildren[--oldEndIndex];
      newEndVnode = newChildren[--newEndIndex];
    } else if (isSameVnode(oldStartVnode, newEndVnode)) { // 交叉比(旧头新尾)
      patch(oldStartVnode, newEndVnode); // 递归
      // 移动指针元素、开始指针索引+1、结束指针索引-1
      oldStartVnode = oldChildren[++oldStartIndex];
      newEndVnode = newChildren[--newEndIndex];
    } else if (isSameVnode(oldEndVnode, newStartVnode)) { // 交叉比(旧尾新头)
      patch(oldEndVnode, newStartVnode); // 递归
      oldEndVnode = oldChildren[--oldEndIndex];
      newStartVnode = newChildren[++newStartIndex];
    } else { // 头头、尾尾、交叉头尾、交叉尾头都不同，进行暴力比对，也就是新头和旧头旧尾两者之间的元素进行比对
      let moveIndex = map[newStartVnode.key];
      if (moveIndex === undefined) {
        // 在oldChildren里面没找到，就添加到oldChildren的最前面
        el.insertBefore(createEl(newStartVnode), oldStartVnode.el);
      } else {
        let moveVnode = oldChildren[moveIndex];
        oldChildren[moveIndex] = null; // 将已匹配的旧值设置为null，不会再次找到，且可以防止oldChildren数组塌陷
        // 插入
        el.insertBefore(moveVnode.el, oldStartVnode.el);
        patch(moveVnode, newStartVnode); // 递归
      }
      // 移动指针元素
      newStartVnode = newChildren[++newStartIndex];
    }
  }
  // 添加新的里面的多出的child
  if (newStartIndex <= newEndIndex) {
    for (let i = newStartIndex; i <= newEndIndex; i++) {
      // el.appendChild(createEl(newChildren[i])); // 全部都是appendChild的话，不能保证新dom顺序
      el.insertBefore(createEl(newChildren[i]), oldChildren[i].el);
    }
  }
  // 删除旧的里面多余的child
  if (oldStartIndex <= oldEndIndex) {
    for (let i = oldStartIndex; i <= oldEndIndex; i++) {
      let child = oldChildren[i];
      // child可能为null
      child && el.removeChild(child.el);
    }
  }
}

function updateProperty(vnode, oldProps = {}) {
  let newProps = vnode.data || {};
  let el = vnode.el;

  // 移除操作：将旧的里面存在但新的里面不存在的移除
  for (let key in oldProps) {
    if (!newProps[key]) {
      el.removeAttribute(key);
    }
  }
  let newStyle = newProps?.style || {};
  let oldStyle = oldProps?.style || {};
  for (let key in oldStyle) {
    if (!newStyle[key]) {
      el.style[key] = "";
    }
  }

  // 添加操作
  for (let key in newProps) {
    if (key === 'style') {
      for (let styleName in newProps[key]) {
        el[key][styleName] = newProps[key][styleName];
      }
    } else if (key === 'class') {
      el.className = newProps[key];
    } else {
      el.setAttribute(key, newProps[key]);
    }
  }
}

export function createEl(vnode) {
  let { tag, data, key, children, text } = vnode;
  if (typeof tag === 'string') { // 元素
    vnode.el = document.createElement(tag);
    updateProperty(vnode);
    if (children.length) {
      children.forEach(child => {
        child && vnode.el.appendChild(createEl(child)); // 递归
      })
    }
  } else { // 文本
    vnode.el = document.createTextNode(text);
  }
  return vnode.el;
}

/**
 * vue的渲染流程：
 * 1、初始化状态
 * 2、模板编译(生成ast对象==>生成render字符串==>render函数==>vnode==>dom==>找到父节点、插入、删除旧dom)
 */